#include <z80.h>
#include "kbd.h"

// port address
enum kbd_port {
    KBD_CTRL = 0x5F,
    KBD_DATA = 0x5E
};

// indicator bits
static uint8_t _kbd_indicator_bits = 0;

// keycode to ascii
static const char _kbd_ascii[90] = {
    0, 0, '1', '2', '3', '4', '5', '6', '7', '8', '9', // 0x00 - 0x0A
    '0', '-', '=', 0, 0, 'q', 'w', 'e', 'r', 't', // 0x0B - 0x14
    'y', 'u', 'i', 'o', 'p', '[', ']', 0, 0, 'a', // 0x15 - 0x1E
    's', 'd', 'f', 'g', 'h', 'j', 'k', 'l', ';', '\'', // 0x1F - 0x28
    '`', 0, '\\', 'z', 'x', 'c', 'v', 'b', 'n', // 0x29 - 0x32
    'm', ',', '.', '/', 0, '*', 0, ' ', 0, 0, // 0x33 - 0x3C
    0, 0, 0, 0, 0, 0, 0, 0, 0, '7', // 0x3D - 0x46
    '8', '9', '-', '4', '5', '6', '+', '1', '2', '3', // 0x47 - 0x51
    '0', '.', 0, 0, 0, 0, 0, 0, 0, 0 // 0x52 - 0x5C
};

static char kbd_to_ascii(enum kbd_key key)
{
    uint8_t code = key & 0x7F;
    if (code < KBD_VK_F12)
        return _kbd_ascii[code];
    else
        return 0;
}

static uint8_t kbd_status(void)
{
    return z80_inp(KBD_CTRL);
}

static void kbd_write_cmd(uint8_t data)
{
    while(kbd_status() & KBD_IBF);
    z80_outp(KBD_CTRL, data);
}

static void kbd_write_data(uint8_t data)
{
    while(kbd_status() & KBD_IBF);
    z80_outp(KBD_DATA, data);
}

static uint8_t kbd_poll_key(void)
{
    if (kbd_status() & KBD_OBF)
        return z80_inp(KBD_DATA);
    else
        return 0;
}

static uint8_t kbd_wait_key(void)
{
    while (!(kbd_status() & KBD_OBF));
    return z80_inp(KBD_DATA);
}

static void kbd_set_indicator_active(enum kbd_indicator indicator, bool active)
{
    if (active) {
        _kbd_indicator_bits |= indicator;
    } else {
        _kbd_indicator_bits &= ~indicator;
    }
    kbd_write_data(0xED);
    kbd_write_data(_kbd_indicator_bits);
}

void kbd_init(struct kbd_device *dev)
{
    dev->status = kbd_status;
    dev->poll_key = kbd_poll_key;
    dev->wait_key = kbd_wait_key;
    dev->to_ascii = kbd_to_ascii;
    dev->set_indicator_active = kbd_set_indicator_active;

    kbd_write_cmd(0xAA);
    while (!(kbd_status() & KBD_OBF));
    if (z80_inp(KBD_DATA) != 0x55)
        return;

    // disable mouse, no interrupt, enable translation
    kbd_write_cmd(0x60);
    kbd_write_data(0x60);

    // enable keyboard interface
    kbd_write_cmd(0xAE);
}